package ch.exploadingelephants.model;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import ch.exploadingelephants.graph.MachineGraph;
import ch.exploadingelephants.graph.NirvanaToken;
import ch.exploadingelephants.graph.Token;

/**
 * @author Sandro De Zanet Holds the information to be displayed on the screen
 */
public class Machine implements Serializable {

    Grid<MachineItem> items;

    /**
     * 
     */
    public Machine() {
	this(5, 5);
    }

    /**
     * @param width
     * @param height
     */
    public Machine(int width, int height) {
	items = new Grid<MachineItem>(width, height);
    }

    /**
     * @return
     */
    public int getWidth() {
	return items.getWidth();
    }

    /**
     * @return
     */
    public int getHeight() {
	return items.getHeight();
    }

    /**
     * @param i
     * @param j
     * @return
     */
    public MachineItem get(int i, int j) {
	return items.get(i, j);
    }

    /**
     * @param i
     * @param j
     * @param item
     */
    public void set(int i, int j, MachineItem item) {
	items.set(i, j, item);
    }

    /**
     * @return
     */
    public Grid<MachineItem> getGrid() {
	return items;
    }

    /**
     * @return
     */
    public MachineGraph generateMachineGraph() {
	MachineGraph graph = new MachineGraph();
	try {
	    Point2D startPoint = findStart();
	    Map<Point2D, Token> alreadyVisited = new HashMap<Point2D, Token>();
	    // alreadyVisited.add(startPoint);
	    fillGraphRecursively(startPoint, graph, graph.getStart(),
		    alreadyVisited);
	    System.out.println("-------------");
	} catch (InconsistentMachineException e) {
	    System.err.println(e);
	}
	return graph;
    }

    /**
     * @param startPoint
     * @param graph
     * @param token
     */
    private void fillGraphRecursively(Point2D startPoint, MachineGraph graph,
	    Token token, Map<Point2D, Token> alreadyVisited) {
	System.out.print(startPoint);
	if (!alreadyVisited.containsKey(startPoint)) {
	    alreadyVisited.put(startPoint, token);
	    for (ConveyorDirection direction : ConveyorDirection.values()) {
		Point2D point = direction.createPoint(startPoint);

		if (inLimits(point)) {
		    MachineItem machineItem = items.get(point);
		    if (machineItem != null) {
			Token newToken = alreadyVisited.get(point);
			if (newToken == null) {
			    newToken = machineItem.createGraphObject();
			}
			if (newToken != null) {
			    if (token.tryConnect(newToken, direction)) {
				fillGraphRecursively(point, graph, newToken,
					alreadyVisited);
			    }
			}
		    } else {
			token.tryConnect(new NirvanaToken(), direction);
		    }
		} else {
		    token.tryConnect(new NirvanaToken(), direction);
		}
	    }
	}
    }

    /**
     * @param left
     * @return
     */
    public boolean inLimits(Point2D point) {
	return point.x >= 0 && point.y >= 0 && point.x < getWidth()
		&& point.y < getHeight();
    }

    /**
     * @return
     * @throws InconsistentMachineException
     */
    private Point2D findStart() throws InconsistentMachineException {
	for (int i = 0; i < getWidth(); i++) {
	    for (int j = 0; j < getHeight(); j++) {
		MachineItem machineItem = items.get(i, j);
		if (machineItem != null && machineItem.isStart()) {
		    return new Point2D(i, j);
		}
	    }
	}
	throw new InconsistentMachineException();
    }

    /**
     * @param point
     * @return
     */
    public MachineItem get(Point2D point) {
	return items.get(point);
    }

}
